﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE;
using EnvDTE80;
using System.Text.RegularExpressions;
using Common;
using NamedPipe;

namespace BovespaMaintainabilityRules
{
    [BaseAddIn.Rules.DoubleClass("Rule_BovespaVariablesAtSameCodeBlockOneLine")]
    public sealed class Rule_BovespaVariablesAtSameCodeBlockOneLine_AddIn : BaseAddIn.Rules.BaseRuleAddIn
    {
        #region Fields

        private List<String> bodyMethodLines;
        private List<String> bodyCommandLines;
        private List<VariableFxCop> listVariables;
        private List<Int32> listLines;

        #endregion

        #region Constant

        const string SYMBOL_VARIABLE = @"(<.*>)|(\[.*\])";

        #endregion

        #region Constructor

        public Rule_BovespaVariablesAtSameCodeBlockOneLine_AddIn() :
            base("BovespaVariablesAtSameCodeBlockOneLine", "BovespaMaintainabilityRules.BovespaMaintainabilityRules", typeof(Rule_BovespaVariablesAtSameCodeBlockOneLine_AddIn).Assembly)
        {
        }

        #endregion

        #region Protected Override Methods

        protected sealed override void Check(CodeClass2 classe)
        {
            CodeElement elemento = null;
            List<int> linhasDeclaracao = new List<int>();

            bool isAllowVariablePlace = true;
            bool hasError = false;

            if (!classe.StartPoint.Parent.Parent.FullName.Contains(".Designer."))
            {
                for (int i = 1; i <= classe.Members.Count && !hasError; i++)
                {
                    elemento = classe.Members.Item(i);

                    //BovespaVariablesAtSameCodeBlock
                    if (elemento.Kind != vsCMElement.vsCMElementVariable
                        && elemento.Kind != vsCMElement.vsCMElementProperty
                        && elemento.Kind != vsCMElement.vsCMElementEnum
                        && elemento.Kind != vsCMElement.vsCMElementDelegate)
                    {
                        isAllowVariablePlace = false;
                    }

                    if ((elemento.StartPoint.Line == elemento.EndPoint.Line && elemento.Kind == vsCMElement.vsCMElementVariable) || elemento.Kind == vsCMElement.vsCMElementProperty)
                    {
                        if (!isAllowVariablePlace && (elemento.Kind == vsCMElement.vsCMElementVariable|| elemento.Kind == vsCMElement.vsCMElementProperty))
                        {
                            GeraProblemaAddIn(classe.StartPoint.Parent.Parent.FullName,
                                                     elemento.StartPoint.Line,
                                                     elemento.StartPoint.DisplayColumn,
                                                     "BovespaVariablesAtSameCodeBlockOneLineResolution",
                                                     classe.Name,
                                                     elemento.Name,
                                                     classe.Namespace.Name,
                                                     elemento.Name);

                            hasError = true;
                        }
                        else if (!linhasDeclaracao.Contains(elemento.StartPoint.Line)) //DoNotDeclareMultipleLocalsAndFields
                        {
                            linhasDeclaracao.Add(elemento.StartPoint.Line);
                        }
                        else
                        {
                            GeraProblemaAddIn(elemento.StartPoint.Parent.Parent.FullName,
                                                     elemento.StartPoint.Line,
                                                     elemento.StartPoint.DisplayColumn,
                                                     "BovespaVariablesAtSameCodeBlockOneLineResolution",
                                                     classe.Name,
                                                     elemento.Name,
                                                     ((CodeElement)classe.Parent).Name,
                                                     elemento.Name);
                            hasError = true;
                        }
                    }
                    else
                    {
                        if (elemento.Kind == vsCMElement.vsCMElementVariable)
                        {
                            GeraProblemaAddIn(elemento.StartPoint.Parent.Parent.FullName,
                                                     elemento.StartPoint.Line,
                                                     elemento.StartPoint.DisplayColumn,
                                                     "BovespaVariablesAtSameCodeBlockOneLineResolution",
                                                     classe.Name,
                                                     elemento.Name,
                                                     ((CodeElement)classe.Parent).Name,
                                                     elemento.Name);
                            hasError = true;
                        }
                    }
                }
            }
        }


        protected sealed override void Check(CodeFunction funcao, CodeNamespace namespaceElement, CodeClass2 classe)
        {
            MethodFxCop methodInfo = null;
            Int32? numFistInstructionLine = null;
            CodeElement elemento = null;
            EditPoint edit = null;
            string bodyMethod = string.Empty;

            if (Atributos.StructVariable != null && 
                Atributos.StructVariable.Keys.Contains(namespaceElement.Name) &&
                Atributos.StructVariable[namespaceElement.Name].Keys.Contains(classe.Name) &&
                Atributos.StructVariable[namespaceElement.Name][classe.Name].Contains(new MethodFxCop(funcao.Name, null, null), new MethodFxCop()))
            {
                this.listLines = new List<Int32>();
                this.listVariables = null;
                elemento = funcao as CodeElement;
                edit = elemento.StartPoint.CreateEditPoint();
                bodyMethod = edit.GetText((funcao as CodeElement).EndPoint);
                bodyMethod = Rotinas.GetTextNoComent(bodyMethod);
                bodyMethod = this.FromTo(this.FormatBodyMethod(bodyMethod));
                this.bodyMethodLines = bodyMethod.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).ToList<String>();

                methodInfo = (from m in Atributos.StructVariable[namespaceElement.Name][classe.Name]
                              where m.MethodName == funcao.Name &&
                              m.FistLineInstruction != null &&
                              m.FistLineInstruction >= funcao.StartPoint.Line &&
                              m.FistLineInstruction <= funcao.EndPoint.Line
                             select m).SingleOrDefault();

                if (methodInfo != null)
                {
                    listVariables = methodInfo.VariableList;
                    numFistInstructionLine = methodInfo.FistLineInstruction;
                }

                if (listVariables != null)
                {
                    this.ExtractCommands(bodyMethod);

                    foreach (VariableFxCop variable in listVariables)
                    {                        
                        if (!this.CheckVariableInBlock(variable.VariableType, variable.VariableName, numFistInstructionLine, funcao.StartPoint.Line))
                        {
                            GeraProblemaAddIn(classe.StartPoint.Parent.Parent.FullName,
                                             elemento.StartPoint.Line,
                                             elemento.StartPoint.DisplayColumn,
                                             "BovespaVariablesAtSameCodeBlockOneLineResolution",
                                             classe.Name,
                                             elemento.Name,
                                             classe.Namespace.Name,
                                             variable.VariableName);

                            break;
                        }
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        private bool CheckVariableInBlock(String variableType, String variableName, Int32? numFistInstructionLine, Int32 lineMethodDeclaration)
        {
            int numLine = 0;
            Regex regVariableDeclare = null;
            bool retorno = true;
            string currentInstruction = string.Empty;
            string regKey;

            variableType = variableType.Replace("?", "\\?");
            regKey = this.RemoveSimbolInType(variableType) + @"(\s+)" + variableName + @"(\s+)";

            regVariableDeclare = new Regex(regKey);

            if (numFistInstructionLine != null)
            {
                foreach (String instruction in this.bodyMethodLines.GetRange(0, Convert.ToInt32(numFistInstructionLine) - lineMethodDeclaration - 1))
                {
                    currentInstruction = this.RemoveSimbolInType(instruction);

                    if (!string.IsNullOrEmpty(regVariableDeclare.Match(currentInstruction).Value))
                    {
                        var list = (from v in this.listVariables
                                    where v.VariableName == variableName
                                    select v).SingleOrDefault();

                        if (list != null)
                        {
                            list.NumLine = numLine;

                            if (this.listLines.Contains(numLine))
                                retorno = false;
                            else
                                retorno = true;

                            this.listLines.Add(numLine);
                        }

                        return retorno;
                    }

                    numLine++;
                }
            }

            if (this.bodyCommandLines != null && this.bodyCommandLines.Count > 0)
            {
                foreach (String command in this.bodyCommandLines)
                {
                    currentInstruction = this.RemoveSimbolInType(command);

                    if (!string.IsNullOrEmpty(regVariableDeclare.Match(currentInstruction).Value))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private String FromTo(String texto)
        {
            texto = this.FromIntToInt32(texto);
            texto = this.FromstringToString(texto);
            texto = this.FromdecimalToDecimal(texto);
            texto = this.FromfloatToFloat(texto);
            texto = this.FromboolToBoolean(texto);
            
            return texto;
        }

        private String FromIntToInt32(String texto)
        {
            return texto.Replace(" int ", " Int32 ").Replace(" int? ", " Int32? ");
        }

        private String FromstringToString(String texto)
        {
            return texto.Replace(" string ", " String ");
        }

        private String FromdecimalToDecimal(String texto)
        {
            return texto.Replace(" decimal ", " Decimal ").Replace(" decimal? ", " Decimal? ");
        }

        private String FromfloatToFloat(String texto)
        {
            return texto.Replace(" float ", " Float ").Replace(" float? ", " Float? ");
        }

        private String FromboolToBoolean(String texto)
        {
            return texto.Replace(" bool ", " Boolean ").Replace(" bool? ", " Boolean? ");
        }

        private String FormatBodyMethod(string bodyMethod)
        {
            bodyMethod = bodyMethod.Replace(Environment.NewLine, string.Concat(" ", Environment.NewLine, " "));
            bodyMethod = bodyMethod.Replace("=", " = ");
            bodyMethod = bodyMethod.Replace(";", " ; ");
            bodyMethod = bodyMethod.Replace(",", " , ");
            bodyMethod = bodyMethod.Replace(".", " . ");
            bodyMethod = bodyMethod.Replace("(", " ( ");
            bodyMethod = bodyMethod.Replace(")", " ) ");
            bodyMethod = bodyMethod.Replace("{", " { ");
            bodyMethod = bodyMethod.Replace("}", " } ");
            bodyMethod = bodyMethod.Replace("[", " [ ");
            bodyMethod = bodyMethod.Replace("]", " ] ");
            bodyMethod = bodyMethod.Replace("+", " + ");
            bodyMethod = bodyMethod.Replace("-", " - ");
            bodyMethod = bodyMethod.Replace("*", " * ");
            bodyMethod = bodyMethod.Replace("//", " // ");
            bodyMethod = bodyMethod.Replace("\\", " \\ ");
            bodyMethod = bodyMethod.Replace("?", "? ");
            bodyMethod = bodyMethod.Replace("<", " < ");
            bodyMethod = bodyMethod.Replace(">", " > ");

            return bodyMethod;
        }

        private String RemoveSimbolInType(String texto)
        {
            String result = texto;
            Regex reg = new Regex(SYMBOL_VARIABLE, RegexOptions.None);

            foreach (Match match in reg.Matches(texto))
            {
                result = result.Replace(match.Value, "");
            }

            return result;
        }

        private void ExtractCommands(String textComm)
        {
            string expressFor = @"for(\s+\(|\()";
            string expressForEach = @"foreach(\s+\(|\()";
            string expressUsing = @"using(\s+\(|\()";
            string expressCatch = @"catch(\s+\(|\()";

            StringBuilder strB = new StringBuilder();
            strB.AppendLine(this.CheckRegex(expressFor, CommandVariable.forCommand, textComm));
            strB.AppendLine(this.CheckRegex(expressForEach, CommandVariable.foreachCommand, textComm));
            strB.AppendLine(this.CheckRegex(expressUsing, CommandVariable.usingCommand, textComm));
            strB.AppendLine(this.CheckRegex(expressCatch, CommandVariable.catchCommand, textComm));

            this.bodyCommandLines = this.FromTo(strB.ToString()).Split(new string[] { Environment.NewLine }, StringSplitOptions.None).ToList<String>();
        }

        private String CheckRegex(String express, CommandVariable comm, String textComm)
        {
            Regex reg = new Regex(express, RegexOptions.None);

            StringBuilder strB = new StringBuilder();

            foreach (Match match in reg.Matches(textComm))
            {
                strB.AppendLine(" " + this.GetFullCommand(match.Index, comm, textComm) + " ");
            }

            return strB.ToString();
        }

        private String GetFullCommand(int index, CommandVariable comm, String textComm)
        {
            string retorno = string.Empty;

            switch (comm)
            {
                case CommandVariable.catchCommand:
                    retorno = textComm.Substring(index, textComm.Length - index);

                    if (retorno.IndexOf(')') > 0)
                        retorno = retorno.Substring(0, retorno.IndexOf(')'));
                    else
                        retorno = string.Empty;

                    break;
                case CommandVariable.forCommand:
                    retorno = textComm.Substring(index, textComm.Length - index);

                    if (retorno.IndexOf(';') > 0)
                        retorno = retorno.Substring(0, retorno.IndexOf(';'));
                    else
                        retorno = string.Empty;

                    break;
                case CommandVariable.foreachCommand:
                    retorno = textComm.Substring(index, textComm.Length - index);

                    if (retorno.IndexOf(" in ") > 0)
                        retorno = retorno.Substring(0, retorno.IndexOf(" in "));
                    else
                        retorno = string.Empty;

                    break;
                case CommandVariable.usingCommand:
                    retorno = textComm.Substring(index, textComm.Length - index);

                    if (retorno.IndexOf('=') > 0)
                        retorno = retorno.Substring(0, retorno.IndexOf('='));
                    else
                        retorno = string.Empty;

                    break;
            }

            return retorno;
        }
        
        #endregion
    }

    public enum CommandVariable
    {
        forCommand,
        foreachCommand,
        usingCommand,
        catchCommand
    }
}
